package com.maybach.workflow.core.service;


import com.maybach.util.AssertUtil;
import com.maybach.workflow.common.api.FlowChain;
import com.maybach.workflow.common.api.WorkFlowContext;
import com.maybach.workflow.common.api.enums.*;
import com.maybach.workflow.common.api.node.*;
import com.maybach.workflow.common.model.FlowDO;
import com.maybach.workflow.common.model.FlowTaskActorDO;
import com.maybach.workflow.common.model.FlowTaskDO;
import com.maybach.workflow.common.model.FlowTaskHisDO;
import com.maybach.workflow.core.FlowDefineFactory;
import com.maybach.workflow.core.expression.IExpression;
import com.maybach.workflow.core.repository.IFlowRepository;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * Created by well on 15/6/12.
 */
public class TaskServiceImpl implements TaskService {

    @Autowired
    protected IFlowRepository flowRepository;
    @Autowired
    protected SecurityService securityService;

    @Autowired
    protected IExpression expression;

    private Logger log = LoggerFactory.getLogger(TaskServiceImpl.class);


    private void logFlow(BaseNode nextNode, String desc, Long taskId) {
        /*FlowLogDO flowLogDO = new FlowLogDO();
        if (AudiUser.getUserName() != null && AudiUser.getUserName().getRoles() != null && AudiUser.getUserName().getRoles().length > 0) {
            flowLogDO.setActRole(AudiUser.getUserName().getRoles()[0]);
            flowLogDO.setUserId(AudiUser.getUserName().getUserId());
        }
        flowLogDO.setActType(nextNode.getNodeType().name());
        flowLogDO.setLogDesc(desc);
        flowLogDO.setTaskId(taskId);
        flowLogDAO.insert(flowLogDO);*/

    }


    private void createTaskNode(TaskNode taskNode, FlowDO flowDO, Long parentId, WorkFlowContext context) {
        FlowTaskDO flowTaskDO = new FlowTaskDO();
        flowTaskDO.setDisplayName(taskNode.getDisplayName());
        flowTaskDO.setParentTaskId(parentId);
        flowTaskDO.setTaskName(taskNode.getName());
        flowTaskDO.setTaskState(FlowStateEnum.init.getStatus());
        flowTaskDO.setFlowId(flowDO.getId());
        flowTaskDO.setTaskType(taskNode.getNodeType().name());
        flowTaskDO.setBizId(flowDO.getBizId());
        flowTaskDO.setBizType(flowDO.getBizType());
        flowRepository.createFlowTask(flowTaskDO);
        List<FlowTaskActorDO> list = this.getTaskActor(flowTaskDO, taskNode, context);
        flowRepository.createTaskActors(list);
    }

    @Override
    public List<FlowTaskActorDO> getTaskActor(FlowTaskDO flowTaskDO, TaskNode taskNode, WorkFlowContext context) {
        List<FlowTaskActorDO> list = new ArrayList<FlowTaskActorDO>();
        for (TaskActor taskActor : taskNode.getActorList()) {
            if (StringUtils.isNotBlank(taskActor.getExpression())) {  // 从表达式中获取
                Object object = expression.eval(Object.class, taskActor.getExpression(), context.getBizData());
                if (object instanceof Collection) {
                    for (Object o : (Collection) object) {
                        list.add(this.assemblyTaskActorDO(flowTaskDO, taskActor.getActorType(), o));
                    }
                } else {
                    list.add(this.assemblyTaskActorDO(flowTaskDO, taskActor.getActorType(), object));
                }
            } else {// 从静态值中获取
                list.add(this.assemblyTaskActorDO(flowTaskDO, taskActor.getActorType(), taskActor.getValue()));
            }
        }
        return list;
    }

    protected FlowTaskActorDO  assemblyTaskActorDO(FlowTaskDO flowTaskDO, ActorTypeEnum type , Object value){
        FlowTaskActorDO taskActorDO = new FlowTaskActorDO();
        taskActorDO.setActorType(type.name());
        taskActorDO.setBizId(flowTaskDO.getBizId());
        taskActorDO.setBizType(flowTaskDO.getBizType());
        taskActorDO.setExpireTime(flowTaskDO.getExpireTime());
        taskActorDO.setFlowId(flowTaskDO.getFlowId());
        taskActorDO.setTaskId(flowTaskDO.getId());
        taskActorDO.setTaskName(flowTaskDO.getTaskName());
        taskActorDO.setValue(String.valueOf(value));
        return taskActorDO;
    }




    private FlowTaskHisDO copy2his(FlowDO flowDO, FlowTaskDO flowTaskDO, FlowTaskActorDO taskActorDO) {
        if (flowTaskDO == null || null == flowDO) {
            return null;
        }
        FlowTaskHisDO flowTaskHisDO = new FlowTaskHisDO();
        flowTaskHisDO.setTaskType(flowTaskDO.getTaskType());
        flowTaskHisDO.setTaskId(flowTaskDO.getId());
        flowTaskHisDO.setFlowId(flowTaskDO.getFlowId());
        flowTaskHisDO.setBizId(flowDO.getBizId());
        flowTaskHisDO.setBizType(flowDO.getBizType());
        flowTaskHisDO.setFlowName(flowDO.getFlowName());
        if(null != taskActorDO){
            flowTaskHisDO.setActorType(taskActorDO.getActorType());
            flowTaskHisDO.setActorValue(taskActorDO.getValue());
        }
        flowTaskHisDO.setDisplayName(flowTaskDO.getDisplayName());
        flowTaskHisDO.setExecuteUserId(securityService.getUserId());
        flowTaskHisDO.setTaskName(flowTaskDO.getTaskName());
        flowTaskHisDO.setTaskState(TaskStateEnum.success.getStatus());
        flowTaskHisDO.setParentTaskId(flowTaskDO.getParentTaskId());
        return flowTaskHisDO;

    }



    @Override
    public boolean rejectTask(WorkFlowContext workFlowContext) {
        // 1. 检验当前结点是否能驳回，如果外部没有指定要驳回的结点， 只有audi任务才能驳回
        BaseNode thisNode = workFlowContext.getThisFlowChain().getBaseNode(workFlowContext.getActionName());
        if(null == workFlowContext.getRejectToAction()) {
            if (thisNode.getNodeType() != NodeTypeEnum.task) {
                return false;
            }
            TaskNode taskNode = (TaskNode) thisNode;
            if (taskNode.getTaskType() != TaskEnum.audit) {
                return false;
            }
        }

        // 2. 获取驳回到的结点
        TaskNode parentTask = null;
        if(null != workFlowContext.getRejectToAction()){
            parentTask =  (TaskNode)workFlowContext.getThisFlowChain().getBaseNode(workFlowContext.getRejectToAction());
        }else {
            parentTask = findParentTask(thisNode.getParent(), workFlowContext.getThisFlowChain());
        }
        if (parentTask == null) {
            throw new RuntimeException("unkown parent node: action=" + workFlowContext.getActionName() + ", rejectAction=" + workFlowContext.getRejectToAction());
        }

        // 3. 获取当前所有的任务
        FlowDO thisFlow = workFlowContext.getThisFlow();
        List<FlowTaskDO> taskDOList = flowRepository.getFlowTasksByFlowId(thisFlow.getId());
        Long thisTaskId = null;
        // 4. 对已存在的任务进行处理，删除当前任务和处于驳回任务下游的结点
        for(FlowTaskDO flowTaskDO : taskDOList){

            FlowTaskHisDO flowTaskHisDO = null;
            if(flowTaskDO.getTaskName().equals(workFlowContext.getActionName())){
                // 4.1 直接删除当前在做的任务
                thisTaskId = flowTaskDO.getId();
                flowTaskHisDO = copy2his(thisFlow, flowTaskDO, workFlowContext.getTaskActor());
                flowTaskHisDO.setTaskState(TaskStateEnum.reject.getStatus());
            }else if(isChildNode(workFlowContext.getThisFlowChain(), parentTask.getName(),flowTaskDO.getTaskName(),  new HashSet<String>())){
                // 4.2 驳回到的下游结点要删除
                flowTaskHisDO = copy2his(thisFlow, flowTaskDO, workFlowContext.getTaskActor());
                flowTaskHisDO.setTaskState(TaskStateEnum.init.getStatus());
            }
            if(null != flowTaskHisDO){
                flowRepository.createFlowTaskHis(flowTaskHisDO);
                flowRepository.delFlowTaskById(flowTaskDO.getId());
            }
        }

        // 5. 创建驳回到的结点
        this.createTaskNode(parentTask, thisFlow, thisTaskId, workFlowContext);
        logFlow(thisNode, "审核不通过", thisTaskId);

        return true;
    }

    /**
     * 撤销流程
     * @param context
     * @return
     */
    @Override
    public boolean cancleFlow(WorkFlowContext context) {
        AssertUtil.notNull(context.getThisFlow(), "context.getThisFlow()");
        FlowDO thisFlow = context.getThisFlow();
        FlowChain flowChain = context.getThisFlowChain();
        AssertUtil.notNull(flowChain.getBaseNode("restart"), "flowChain.getBaseNode(\"restart\")");

        List<FlowTaskHisDO> taskHisDOList = flowRepository.getFlowTaskHissByFlowId(thisFlow.getId());
        List<FlowTaskDO> taskDOList = flowRepository.getFlowTasksByFlowId(thisFlow.getId());
        // 1. 将已执行的任务更改为 回滚状态
        for(FlowTaskHisDO taskHisDO : taskHisDOList){
            //更新为已回滚状态  flowRepository.
            flowRepository.updateTaskHistState(taskHisDO.getId(), TaskStateEnum.rollback.getStatus());
        }
        // 2. 删除已经存在的任务，并存储在任务历史中 状态是未执行
        for(FlowTaskDO taskDO : taskDOList){
            FlowTaskHisDO flowTaskHisDO = copy2his(thisFlow, taskDO, context.getTaskActor());
            flowTaskHisDO.setTaskState(TaskStateEnum.init.getStatus());
            flowRepository.createFlowTaskHis(flowTaskHisDO);
            flowRepository.delFlowTaskById(taskDO.getId());
        }

        // 3. 创建驳回到的结点
        this.createTaskNode((TaskNode)flowChain.getBaseNode("restart"), thisFlow, 0L, context);

        flowRepository.updateFlowStatus(thisFlow.getId(), FlowStateEnum.cancle.getStatus());
        //logFlow(thisNode, "流程被撤销", 0L);

        return true;
    }


    private TaskNode findParentTask(String parentNodeName, FlowChain flowChain) {
        BaseNode parent = flowChain.getBaseNode(parentNodeName);
        if (parent == null) {
            throw new RuntimeException("unkown parent node:" + parentNodeName);
        }
        switch (parent.getNodeType()) {
            case task:
                if (parent.getNodeType() == NodeTypeEnum.task) {
                    return (TaskNode) parent;
                } else {
                    return findParentTask(parent.getParent(), flowChain);
                }
            default:
                return findParentTask(parent.getParent(), flowChain);
        }



    }


    private TaskNode findParentTask(String parentNodeName, String flowName, Integer flowVersion) {
        FlowChain flowChain = FlowDefineFactory.getFlowDefine(flowName, flowVersion);
        BaseNode parent = flowChain.getBaseNode(parentNodeName);
        if (parent == null) {
            throw new RuntimeException("unkown parent node:" + parentNodeName);
        }
        if (parent.getNodeType() == NodeTypeEnum.task) {
            return (TaskNode) parent;
        } else {
            return findParentTask(parent.getParent(), flowName, flowVersion);
        }
    }

    @Override
    public  boolean isChildNode(FlowChain flowChain, String currentNodeName, String checkNodeName, Set<String> visitSet) {
        if(StringUtils.isBlank(currentNodeName)){
            return false;
        }
        if (visitSet.contains(currentNodeName)) {
            return false;
        }
        visitSet.add(currentNodeName);
        if (currentNodeName.equals(checkNodeName)) {
            return true;
        }
        BaseNode currentNode = flowChain.getBaseNode(currentNodeName);
        if (currentNode == null) {
            return false;
        }

        if (currentNode.getNodeType() == NodeTypeEnum.fork) {
            ForkNode forkNode = (ForkNode) currentNode;
            List<BaseNode> forkNodeList = forkNode.getForkNodeList();
            for (BaseNode baseNode : forkNodeList) {
                boolean isChild = isChildNode(flowChain, baseNode.getName(), checkNodeName, visitSet);
                if (isChild) {
                    return true;
                }
            }
        } else if (currentNode.getNodeType() == NodeTypeEnum.decision) {
            DecisionNode decisionNode = (DecisionNode) currentNode;
            for (TransactionNode transactionNode : decisionNode.getTransactionNodeList()) {
                boolean isChild = isChildNode(flowChain, transactionNode.getTo(), checkNodeName, visitSet);
                if (isChild) {
                    return true;
                }
            }

        }

        return isChildNode(flowChain, currentNode.getTo(), checkNodeName, visitSet);
    }
}
